package com.kokotchy.betaSeriesAPI.api.xmlImpl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dom4j.Document;
import org.dom4j.Node;

import com.kokotchy.betaSeriesAPI.Utils;
import com.kokotchy.betaSeriesAPI.UtilsXml;
import com.kokotchy.betaSeriesAPI.api.BetaseriesApiException;
import com.kokotchy.betaSeriesAPI.api.Constants;
import com.kokotchy.betaSeriesAPI.api.IMembers;
import com.kokotchy.betaSeriesAPI.api.NotImplementedException;
import com.kokotchy.betaSeriesAPI.api.factories.EpisodeFactory;
import com.kokotchy.betaSeriesAPI.api.factories.FriendFactory;
import com.kokotchy.betaSeriesAPI.api.factories.MemberFactory;
import com.kokotchy.betaSeriesAPI.api.factories.NotificationFactory;
import com.kokotchy.betaSeriesAPI.model.Episode;
import com.kokotchy.betaSeriesAPI.model.Friend;
import com.kokotchy.betaSeriesAPI.model.Member;
import com.kokotchy.betaSeriesAPI.model.Notification;
import com.kokotchy.betaSeriesAPI.model.SortType;
import com.kokotchy.betaSeriesAPI.model.SubtitleLanguage;

/**
 * Members API
 * 
 * @author kokotchy
 */
public class Members implements IMembers {

	/**
	 * API Key
	 */
	private String apiKey;

	/**
	 * Create new members api with the given key
	 * 
	 * @param apiKey
	 *            API Key
	 */
	public Members(String apiKey) {
		this.apiKey = apiKey;
	}

	@Override
	public boolean addFriend(String token, String login) {
		throw new NotImplementedException();
	}

	@Override
	public String auth(String login, String password)
			throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.MEMBER_LOGIN, login);
		params.put(Constants.MEMBER_PASSWORD, Utils.getMD5(password));
		Document document = UtilsXml.executeQuery("members/auth", apiKey,
				params);
		if (UtilsXml.isOk(document)) {
			Node tokenNode = document.selectSingleNode("/root/member/token");
			return tokenNode.getText();
		}
		return null;
	}

	@Override
	public boolean destroy(String token) throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.TOKEN, token);
		Document document = UtilsXml.executeQuery("members/destroy", apiKey,
				params);
		return UtilsXml.isOk(document);
	}

	@Override
	public Set<String> getBadges(String token) {
		throw new NotImplementedException();
	}

	@Override
	public int getDateCache(String token, boolean identifieduser)
			throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.NO_DATA, "1");
		String action;
		if (identifieduser) {
			action = "members/infos";
			params.put(Constants.TOKEN, token);
		} else {
			action = "members/infos/" + token;
		}
		Document document = UtilsXml.executeQuery(action, apiKey, params);
		if (UtilsXml.isOk(document)) {
			return UtilsXml.getInt(document, Constants.CACHED);
		}
		return -1;
	}

	@Override
	public Set<Episode> getEpisodes(String token,
			SubtitleLanguage subtitleLanguage) throws BetaseriesApiException {
		return getEpisodesList(token, subtitleLanguage, false);
	}

	@Override
	public Set<Episode> getEpisodes(String token,
			SubtitleLanguage subtitleLanguage, boolean onlyNext)
			throws BetaseriesApiException {
		return getEpisodesList(token, subtitleLanguage, onlyNext);
	}

	/**
	 * Return the list of episodes to watch for the logged user. The language of
	 * the subtitle is specified with subtitleLanguage. If onlyNext is set, only
	 * the first episode by show is returned.
	 * 
	 * @param token
	 *            Token of the user
	 * @param subtitleLanguage
	 *            Language of the subtitles
	 * @param onlyNext
	 *            If set, only one episode by show
	 * @return List of show
	 * @throws BetaseriesApiException
	 */
	@SuppressWarnings("unchecked")
	private Set<Episode> getEpisodesList(String token,
			SubtitleLanguage subtitleLanguage, boolean onlyNext)
			throws BetaseriesApiException {
		String lang = null;
		switch (subtitleLanguage) {
		case VF:
			lang = Constants.LANG_VF;
			break;
		case VOVF:
			lang = Constants.LANG_VOVF;
			break;
		case ALL:
			lang = Constants.LANG_ALL;
			break;
		}
		Set<Episode> result = new HashSet<Episode>();
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.TOKEN, token);
		if (onlyNext) {
			params.put(Constants.MEMBER_VIEW, Constants.MEMBER_NEXT);
		}
		Document document = UtilsXml.executeQuery("members/episodes/" + lang,
				apiKey, params);
		if (UtilsXml.isOk(document)) {
			List<Node> nodes = document.selectNodes("/root/episodes/episode");
			if (nodes.size() > 0) {
				for (Node node : nodes) {
					result.add(EpisodeFactory.createEpisode(node));
				}
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Set<Friend> getFriends(String token) throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put("token", token);
		Document document = UtilsXml.executeQuery("members/friends", apiKey,
				params);
		if (UtilsXml.isOk(document)) {
			Set<Friend> result = new HashSet<Friend>();
			List<Node> friends = document.selectNodes("/root/friends/friend");
			if (friends.size() > 0) {
				for (Node node : friends) {
					String text = node.getText();
					Friend friend = FriendFactory.createFriend(text);
					result.add(friend);
				}
			}
			return result;
		}
		return null;
	}

	/**
	 * Return the information about the user. If it is the identified user,
	 * identifiedUser has to be true and user has to be the token. If
	 * identifiedUser is false, then the user is the login of the user to
	 * retrieve.
	 * 
	 * @param user
	 *            User or token to retrieve
	 * @param identifiedUser
	 *            If user if the user or the token
	 * @param lastCache
	 * @return Member informations
	 * @throws BetaseriesApiException
	 */
	private Member getInfosForUser(String user, boolean identifiedUser,
			int lastCache) throws BetaseriesApiException {
		Document document;
		Map<String, String> params = new HashMap<String, String>();
		if (lastCache > 0) {
			params.put(Constants.SINCE, "" + lastCache);
		}
		if (!identifiedUser) {
			document = UtilsXml.executeQuery("members/infos/" + user, apiKey,
					params);
		} else {
			params.put(Constants.TOKEN, user);
			document = UtilsXml.executeQuery("members/infos", apiKey, params);
		}
		if (UtilsXml.isOk(document)) {
		return MemberFactory.createMember(document
				.selectSingleNode("/root/member"));
		}
		return null;
	}

	@Override
	public Set<Notification> getNotifications(String token, boolean seen,
			int nb, int lastId, SortType sort) throws BetaseriesApiException {
		return getNotificationsWithParameters(token, seen, nb, lastId, sort);
	}

	@Override
	public Set<Notification> getNotifications(String token, boolean seen,
			int nb, SortType sort) throws BetaseriesApiException {
		return getNotificationsWithParameters(token, seen, nb, -1, sort);
	}

	@Override
	public Set<Notification> getNotifications(String token, boolean seen,
			SortType sort) throws BetaseriesApiException {
		return getNotificationsWithParameters(token, seen, -1, -1, sort);
	}

	@Override
	public Set<Notification> getNotifications(String token, int nb,
			SortType sort) throws BetaseriesApiException {
		return getNotificationsWithParameters(token, null, nb, -1, sort);
	}

	/**
	 * Return the notifications with the given parameter. Conditions for the
	 * parameters to be used:
	 * <ul>
	 * <li>seen has not to be null</li>
	 * <li>nb greater than 0</li>
	 * <li>lastId greater than 0</li>
	 * </ul>
	 * 
	 * @param token
	 *            Token of the logged user
	 * @param seen
	 *            If the notification has to be already seen or not
	 * @param nb
	 *            Number of notification
	 * @param lastId
	 *            Start of notification
	 * @param sort
	 * @return List of notification
	 * @throws BetaseriesApiException
	 */
	@SuppressWarnings("unchecked")
	private Set<Notification> getNotificationsWithParameters(String token,
			Boolean seen, int nb, int lastId, SortType sort)
			throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		if (seen != null) {
			params.put(Constants.SEEN, seen ? Constants.YES : Constants.NO);
		}

		switch (sort) {
		case ASC:
			params.put(Constants.SORT, Constants.ORDER_ASC);
			break;
		case DESC:
			params.put(Constants.SORT, Constants.ORDER_DESC);
			break;
		default:
		}

		if (nb > 0) {
			params.put(Constants.LIMIT, "" + nb);
		}
		if (lastId > 0) {
			params.put(Constants.MEMBER_LAST_ID, "" + lastId);
		}
		params.put(Constants.TOKEN, token);
		Document document = UtilsXml.executeQuery("members/notifications",
				apiKey, params);
		List<Node> nodes = document
				.selectNodes("/root/notifications/notification");
		Set<Notification> notifications = new HashSet<Notification>();
		if (nodes.size() > 0) {
			for (Node node : nodes) {
				notifications.add(NotificationFactory.createNotification(node));
			}
		}
		return notifications;
	}

	@Override
	public Set<String> getUserBadges(String login) {
		throw new NotImplementedException();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Set<Friend> getUserFriends(String user)
			throws BetaseriesApiException {
		Document document = UtilsXml.executeQuery("members/friends/" + user,
				apiKey);
		if (UtilsXml.isOk(document)) {
			Set<Friend> result = new HashSet<Friend>();
			List<Node> friends = document.selectNodes("/root/friends/friend");
			if (friends.size() > 0) {
				for (Node node : friends) {
					String text = node.getText();
					Friend friend = FriendFactory.createFriend(text);
					result.add(friend);
				}
			}
			return result;
		}
		return null;
	}

	@Override
	public Member infos(String token) throws BetaseriesApiException {
		return getInfosForUser(token, true, -1);
	}

	@Override
	public Member infos(String token, int lastCache)
			throws BetaseriesApiException {
		return getInfosForUser(token, true, lastCache);
	}

	@Override
	public Member infosOfUser(String user) throws BetaseriesApiException {
		return getInfosForUser(user, false, -1);
	}

	@Override
	public Member infosOfUser(String user, int lastCache)
			throws BetaseriesApiException {
		return getInfosForUser(user, false, lastCache);
	}

	@Override
	public boolean isActive(String token) throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.TOKEN, token);
		Document document = UtilsXml.executeQuery("members/is_active", apiKey,
				params);
		Node node = document.selectSingleNode("/root");
		return UtilsXml.getBoolean(node, Constants.ERROR_CODE);
	}

	@Override
	public boolean rate(String token, String url, int season, int episode,
			int rate) throws BetaseriesApiException {
		if (rate > 0 && rate <= 5) {
			Map<String, String> params = new HashMap<String, String>();
			params.put(Constants.TOKEN, token);
			params.put(Constants.SEASON, "" + season);
			params.put(Constants.EPISODE, "" + episode);
			params.put(Constants.RATE, "" + rate);
			Document document = UtilsXml.executeQuery("members/note/" + url,
					apiKey, params);
			if (UtilsXml.isOk(document))
				return true;
		}
		return false;
	}

	@Override
	public boolean removeFriend(String token, String login) {
		throw new NotImplementedException();
	}

	@Override
	public boolean resetViewedShow(String token, String url)
			throws BetaseriesApiException {
		return setWatched(token, url, 0, 0);
	}

	@Override
	public Set<String> search(String login) {
		throw new NotImplementedException();
	}

	@Override
	public boolean setDownloaded(String token, String url, int season,
			int episode) throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.SEASON, "" + season);
		params.put(Constants.EPISODE, "" + episode);
		params.put(Constants.TOKEN, token);
		Document document = UtilsXml.executeQuery("members/downloaded/" + url,
				apiKey, params);
		if (UtilsXml.isOk(document)) {
			Node root = document.selectSingleNode("/root");
			return UtilsXml.getBoolean(root, "downloaded");
		}
		return false;
	}

	@Override
	public boolean setWatched(String token, String url, int season, int episode)
			throws BetaseriesApiException {
		Map<String, String> params = new HashMap<String, String>();
		params.put(Constants.SEASON, "" + season);
		params.put(Constants.EPISODE, "" + episode);
		params.put(Constants.TOKEN, token);
		Document document = UtilsXml.executeQuery("members/watched/" + url,
				apiKey, params);
		if (UtilsXml.isOk(document))
			return false;
		// TODO Check for error
		return true;
	}

	@Override
	public boolean signup(String login, String password, String email) {
		throw new NotImplementedException();
	}

	@Override
	public String oauth() throws BetaseriesApiException {
		Document document = UtilsXml.executeQuery("members/oauth", apiKey);
		if (UtilsXml.isOk(document)) {
			Node tokenNode = document.selectSingleNode("/root/oauth/key");
			return tokenNode.getText();
		}
		return null;
	}

}
